﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Istrib.Sound;
using Istrib.Sound.Formats;
using System.Media;
using System.ComponentModel;
using NeuralNetwork;
using DigitsRecognizer.UI;
using TheVoiceOfSnake.Models;


namespace TheVoiceOfSnake
{
    public class SpeechRecognizer : Component
    {
        const int HEADER_SIZE = 44;
        const int CHANNELS = 1;
        const short BITS_PER_SAMPLE = 8;
        const int SAMPLE_RATE = 11025;
        const int NUM_OF_CHUNKS = 20;
        const int SPEECH_COOLDOWN = 3;
        const int SPEECH_MIN_LENGTH = 8000; // num of samples
        const int SPEECH_MAX_LENGTH = 20000; // num of samples
        const float SPEECH_THRESHOLD = 0.8f;
        const long STREAM_MAX_LENGTH = 60000;

        private Mp3SoundCapture mp3SoundCapture;
        private PcmSoundFormat waveFormat = PcmSoundFormat.Pcm11kHz8bitMono;
        private MemoryStream ms;
        private int wordsCount;
        private int previousLength;
        private int currentLength;
        private int lengthIncrease;
        private int speechStart;
        private int speechEnd = 0;
        private int speechCooldown;
        private int speechLength;

        private List<Frame> Frames;
        private List<double> ExtractedParameters;
        private Segmentator Segmentator;
        private HammingWindowHandler Windowing;
        private FastFourierTransformer FFT;
        private MFCCHandler MFCC;
        private Writer<double> Writer;

        private Thread thread;
        private PatternsForm PatternsForm;
        private GameForm GameForm;
        private double CommandValue;
        private bool IsGame;
        private string Login = "test";
        private double EdgeDefect = 0.001;

        private NeuralNetwork<double> NeuralNetwork;

        delegate void SetTextCallback(string text);
        delegate void SetDataCallback(double text);
        public SpeechRecognizer(PatternsForm _form)
        {
            PatternsForm = _form;
            FillConstans();
        }

        public SpeechRecognizer(GameForm _form)
        {
            GameForm = _form;
            FillConstans();
        }

        private void FillConstans()
        {
            mp3SoundCapture = new Mp3SoundCapture();
            mp3SoundCapture.CaptureDevice = SoundCaptureDevice.Default;
            mp3SoundCapture.OutputType = Mp3SoundCapture.Outputs.RawPcm;
            mp3SoundCapture.WaveFormat = waveFormat;
            previousLength = 0;
            currentLength = 0;
            lengthIncrease = 0;
            speechStart = 0;
            speechEnd = 0;
            speechCooldown = 0;
            speechLength = 0;
            wordsCount = 0;
            ms = new MemoryStream();
            Frames = new List<Frame>();
            ExtractedParameters = new List<double>();
            Segmentator = new Segmentator();
            Windowing = new HammingWindowHandler();
            FFT = new FastFourierTransformer();
            MFCC = new MFCCHandler();
            Writer = new Writer<double>();
            NeuralNetwork = new NeuralNetwork<double>();
        }

        public void ExtractParametersToDoubleFormat(byte[] input)
        {
            DataProcessing(input);
        }
        public string ExtractParametersToStringFormat(byte[] input) 
        {
            DataProcessing(input);
            
            return Writer.GetParams(ExtractedParameters, CommandValue);
            
            /* wersja z IFFT */
            //Frame result = new Frame();
            //result.SetTableOfValues(MFCC.ExtractFinalParameters(Frames, Segmentator.Frequency));
            //List<Frame> tmp = new List<Frame>();
            //tmp.Add(result);
            //FFT.Transform(tmp, FastFourierTransformationType.IFFT);
            //Writer.SavaParams(tmp.First().GetTableOfValues());
        }

        private void DataProcessing(byte[] input)
        {
            ExtractedParameters = new List<double>();
            if (Frames.Count > 0)
            {
                Frames = new List<Frame>();
            }
            Frames.AddRange(Segmentator.SegmentInputValues(input));
            Windowing.Windowing(Frames);
            FFT.Transform(Frames, FastFourierTransformationType.FFT);
            MFCC.ComputeModuleValues(Frames);
            ExtractedParameters.AddRange(MFCC.ExtractFinalParameters(Frames, Segmentator.Frequency));
        }

        private bool CheckForPotentialSpeech(byte[] buffer, int probability)
        {
            float length = (float)buffer.Length;
            float correction = Math.Max(0f, (float)probability) * 0.1f;
            float threshold = SPEECH_THRESHOLD * (float)length - correction;
            long intensity = 0;
            foreach (byte b in buffer)
            {
                intensity += Math.Abs(b - 128);
            }
            if ((float)intensity >= threshold)
            {
                return true;
            }
            return false;
        }

        private void ProcessSpeech(int speechStart, int speechLength)
        {
            byte[] speechWave = new byte[speechLength];
            ms.Seek(speechStart, SeekOrigin.Begin);
            ms.Read(speechWave, 0, speechLength);

            if (IsGame)
            {
                ExtractedParameters = new List<double>();
                ExtractParametersToDoubleFormat(speechWave);
                double[] results = ExtractedParameters.ToArray();
                double result = NeuralNetwork.EstimateOutput(results, Login, EdgeDefect);

                if (GameForm.InvokeRequired)
                {
                    // It's on a different thread, so use Invoke.
                    SetDataCallback d = new SetDataCallback(GameForm.CheckParametersString);
                    GameForm.Invoke
                        (d, new object[] { result });
                }
                else
                {
                    // It's on the same thread, no need for Invoke 
                    GameForm.CheckParametersString(result);
                }
            }
            else 
            {
                string parametersString = ExtractParametersToStringFormat(speechWave);
                if (PatternsForm.InvokeRequired)
                {
                    // It's on a different thread, so use Invoke.
                    SetTextCallback d = new SetTextCallback(PatternsForm.AddParametersString);
                    PatternsForm.Invoke
                        (d, new object[] { parametersString });
                }
                else
                {
                    // It's on the same thread, no need for Invoke 
                    PatternsForm.AddParametersString(parametersString);
                }
            }
            
            //PrepareWavFromPcmBuffer(speechWave);
            //Console.WriteLine("Speech detected and saved!");
        }

        private void ResetStream()
        {
            ms.SetLength(0);
            ms.Position = 0;
        }

        private void ProcessNewWaveData()
        {
            byte[] buffer = new byte[lengthIncrease];
            ms.Seek(previousLength, SeekOrigin.Begin);
            ms.Read(buffer, 0, (int)lengthIncrease);
            if (CheckForPotentialSpeech(buffer, speechCooldown - 1)) //speech detected
            {
                if (speechCooldown == 0)
                {
                    speechStart = Math.Max(0, previousLength - lengthIncrease/2);
                }
                speechLength = currentLength - speechStart;
                speechCooldown = SPEECH_COOLDOWN;
                if (speechLength >= SPEECH_MAX_LENGTH)
                {
                    Console.WriteLine("max");//
                    wordsCount++;//
                    ProcessSpeech(speechStart, speechLength);
                    speechCooldown = 0;
                    if (currentLength >= STREAM_MAX_LENGTH)
                    {
                        ResetStream();
                    }
                }
            } // speech detected
            else // speech not detected
            {
                if (speechCooldown > 0)
                {
                    speechCooldown--;
                    if (speechCooldown == 0) // end of speech
                    {
                        speechEnd = previousLength;
                        speechLength = speechEnd - speechStart;
                        if (speechLength > SPEECH_MIN_LENGTH)
                        {
                            Console.WriteLine("min");//
                            wordsCount++;//
                            ProcessSpeech(speechStart, speechLength);
                            ResetStream();
                        }
                    }
                }
                else if (currentLength >= STREAM_MAX_LENGTH)
                {
                    ResetStream();
                }
            } // speech not detected
        }

        public void StartRecognizing()
        {
            while (true)
            {
                Thread.Sleep(125);
                previousLength = currentLength;
                currentLength = (int)ms.Length;
                lengthIncrease = currentLength - previousLength;
                if (lengthIncrease > 0) // new data in stream
                {
                    ProcessNewWaveData();
                }
            }
        }

        public void StartCapturing()
        {
            mp3SoundCapture.Start(ms);
        }

        public void StopCapturing()
        {
            mp3SoundCapture.Stop();
        }

        public void Start()
        {
            ThreadStart wstart = new ThreadStart(StartRecognizing);
            thread = new Thread(wstart);
            thread.Start();
        }

        public void Stop()
        {
            thread.Abort();
        }

        public void AddCommandValue(double val) 
        {
            CommandValue = val;
        }

        public void IsGameMode(bool val)
        {
            IsGame = val;
        }

        public void SetLogin(string login) 
        {
            Login = login;
        }
    }
}
